package com.liuyucheng.idea_springboot.wechat.mini.service.impl;

import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.toolkit.IdWorker;
import com.google.common.base.Charsets;
import com.google.common.hash.Hashing;
import com.liuyucheng.idea_springboot.config.RestTemplateConfiguration;
import com.liuyucheng.idea_springboot.constant.RedisKeyConstant;
import com.liuyucheng.idea_springboot.util.IOUtil;
import com.liuyucheng.idea_springboot.util.RedisUtil;
import com.liuyucheng.idea_springboot.util.SnowFlake;
import com.liuyucheng.idea_springboot.wechat.gzh.entity.WechatToken;
import com.liuyucheng.idea_springboot.wechat.mini.entity.*;
import com.liuyucheng.idea_springboot.wechat.mini.service.WeChatMiniService;
import com.liuyucheng.util.EmptyUtil;
import com.tencentcloudapi.common.Credential;
import com.tencentcloudapi.common.exception.TencentCloudSDKException;
import com.tencentcloudapi.common.profile.ClientProfile;
import com.tencentcloudapi.common.profile.HttpProfile;
import com.tencentcloudapi.faceid.v20180301.FaceidClient;
import com.tencentcloudapi.faceid.v20180301.models.*;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.io.Resource;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
import org.springframework.util.Assert;
import org.springframework.web.client.RestClientException;
import org.springframework.web.client.RestTemplate;

import javax.servlet.http.HttpServletResponse;
import java.io.InputStream;
import java.util.*;
import java.util.concurrent.TimeUnit;

/**
 * 微信服务统一调用服务层
 */
@Service
@Slf4j
public class WeChatMiniServiceImpl implements WeChatMiniService {

    @Autowired
    private RedisUtil redisUtil;

    @Value("${wechat.mini.appId}")
    private String appId;
    @Value("${wechat.mini.appSecret}")
    private String appSecret;
    @Value("${wechat.host}")
    private String host;

//    private static String appId = "wxddd2faa52c1e842b";
//    private static String secret = "4f714cab68567f039b6a7795deb452e8";
    private static String ruleId = "3";
    private static String secretId = "AKIDymKcdNr40f29zYpqa3FBqELAk47uSjjR";
    private static String secretKey = "NXR4QVJq77qITO1NREkzxe4D5g15rOec";

    //去码头整饼干
//    private static String appId = "wxdc9e91a2e4a285d8";
//    private static String secret = "57773184ddf17e7c5353a0b24bfd42da";

//    private static String appId = "wx6f53f677f28a7dd6";
//    private static String secret = "fef4a6f6fadea0155a342074de241692";


    /**
     * 更新微信接口的access_token
     */
    @Override
    public String updateAccessToken() {
        String url = host+"/cgi-bin/token?grant_type=client_credential&appid={appId}&secret={appSecret}";
        Map<String, Object> paramMap = new HashMap<>();
        paramMap.put("appId", appId);
        paramMap.put("appSecret", appSecret);
        ResponseEntity<WechatToken> entity = null;
        try {
            entity = new RestTemplate().getForEntity(url, WechatToken.class, paramMap);
        } catch (RestClientException e) {
            log.info("[更新access_token失败]");
            e.printStackTrace();
            return "";
        }
        WechatToken wechatToken = entity.getBody();
        if (!EmptyUtil.isEmpty(wechatToken.getErrcode())) {
            log.info("wechatToken：{}", wechatToken.getErrcode()+","+wechatToken.getErrmsg());
            Assert.isTrue("0".equals(wechatToken.getErrcode()), wechatToken.getErrmsg());
        }
        String accessToken = wechatToken.getAccess_token();
        redisUtil.setEx(RedisKeyConstant.WECHAT_MINI_ACCESS_TOKEN, accessToken, 7100, TimeUnit.SECONDS);
        return accessToken;
    }

    /**
     * 获取accessToken
     * @return
     */
    @Override
    public synchronized String getAccessToken(){
        Object accessToken = redisUtil.get(RedisKeyConstant.WECHAT_MINI_ACCESS_TOKEN);
        if(EmptyUtil.isEmpty(accessToken)){
            return updateAccessToken();
        }
        return accessToken.toString();
    }

    @Override
    public String generateUrlLink() {
//        String url = host + "/wxa/generate_urllink?access_token=" + getAccessToken();//40002, errmsg=invalid grant_type
        String url = host + "/wxa/generatescheme?access_token=" + getAccessToken();//40002, errmsg=invalid grant_type
        //短链接
//        String url = host + "/cgi-bin/shorturl?access_token=" + getAccessToken();//This API has been unsupported
        Map<String, Object> paramMap = new HashMap<>();
//        paramMap.put("page", "pages/index/index");
//        paramMap.put("scene", "a=1");
//        paramMap.put("check_path", false);
//        paramMap.put("env_version", "release");
        ResponseEntity<Map> entity = null;
        try {
            //通过Resource类来接收content type [image/jpeg]的响应数据
            entity = new RestTemplate().postForEntity(url, paramMap, Map.class);
            log.info("generateUrlLink返回结果:{}", entity);
//            Resource resource = entity.getBody();
//            if(resource!=null){
//                InputStream inputStream = resource.getInputStream();
//                byte[] bytes = IOUtil.read(inputStream);
//            }

        } catch (Exception e) {
            log.error("[generateUrlLink调用失败]", e);
        }
        return "";
    }

    /**
     * 通过code换取openId
     * @param code 用户code令牌
     * @return openId
     */
    @Override
    public String getOpenId(String code) {
        String url = "https://api.weixin.qq.com/sns/jscode2session" +
                "?appid={appid}&secret={secret}&js_code={js_code}&grant_type={grant_type}";
        Map<String, Object> paramMap = new HashMap<>();
        paramMap.put("appid", appId);
        paramMap.put("secret", appSecret);
        paramMap.put("js_code", code);
        paramMap.put("grant_type", "authorization_code");


        ResponseEntity<Resource> entity = null;
        String result = "";
        try {
            //通过Resource类来接收content type [image/jpeg]的响应数据
            entity  = new RestTemplate(RestTemplateConfiguration.generateHttpRequestFactory()).getForEntity(url, Resource.class, paramMap);
            Resource resource = entity.getBody();
            if(resource!=null){
                InputStream inputStream = resource.getInputStream();
                byte[] bytes = IOUtil.read(inputStream);
                result = new String(bytes);
            }

        } catch (Exception e) {
            log.error("[更新access_token失败]",e);
        }
        return result;
    }

    /**
     * 获取BizToken
     * @param bizTokenRequest 请求参数
     * @return 结果
     */
    public BizTokenResponse detectAuth(BizTokenRequest bizTokenRequest){
        log.info("获取BizToken请求参数,{},{},{}",
                bizTokenRequest.getIdCard(),
                bizTokenRequest.getName(),
                bizTokenRequest.getRedirectUrl());
        BizTokenResponse bizTokenResponse = new BizTokenResponse();
        try{
            // 实例化一个认证对象，入参需要传入腾讯云账户 SecretId 和 SecretKey，此处还需注意密钥对的保密
            // 代码泄露可能会导致 SecretId 和 SecretKey 泄露，并威胁账号下所有资源的安全性。以下代码示例仅供参考，建议采用更安全的方式来使用密钥，请参见：https://cloud.tencent.com/document/product/1278/85305
            // 密钥可前往官网控制台 https://console.cloud.tencent.com/cam/capi 进行获取
            Credential cred = new Credential(secretId, secretKey);
            // 实例化一个http选项，可选的，没有特殊需求可以跳过
            HttpProfile httpProfile = new HttpProfile();
            httpProfile.setEndpoint("faceid.tencentcloudapi.com");
            // 实例化一个client选项，可选的，没有特殊需求可以跳过
            ClientProfile clientProfile = new ClientProfile();
            clientProfile.setHttpProfile(httpProfile);
            // 实例化要请求产品的client对象,clientProfile是可选的
            FaceidClient client = new FaceidClient(cred, "", clientProfile);
            // 实例化一个请求对象,每个接口都会对应一个request对象
            DetectAuthRequest req = new DetectAuthRequest();
            req.setIdCard(bizTokenRequest.getIdCard());
            req.setName(bizTokenRequest.getName());
            req.setRedirectUrl(bizTokenRequest.getRedirectUrl());
            req.setRuleId(ruleId);
            Encryption encryption1 = new Encryption();
            encryption1.setAlgorithm("0");
            req.setEncryption(encryption1);

            // 返回的resp是一个DetectAuthResponse的实例，与请求对象对应
            DetectAuthResponse resp = client.DetectAuth(req);
            // 输出json格式的字符串回包
            log.info("获取BizToken,结果:{}",DetectAuthResponse.toJsonString(resp));
            bizTokenResponse.setUrl(resp.getUrl());
            bizTokenResponse.setBizToken(resp.getBizToken());
            bizTokenResponse.setRequestId(resp.getRequestId());
        } catch (TencentCloudSDKException e) {
            log.error("detectAuth出现异常",e);
        }

        //挡板
//        bizTokenResponse.setUrl("用于发起核身流程的URL，仅微信H5场景使用");
//        bizTokenResponse.setBizToken("bizToken");
//        bizTokenResponse.setRequestId("唯一请求 ID，每次请求都会返回。定位问题时需要提供该次请求的 RequestId。");

        return bizTokenResponse;
    }

    /**
     * 获取识别结果
     * @param detectInfoRequest 请求参数
     * @return 结果
     */
    public DetectInfoResponse getDetectInfoEnhanced(DetectInfoRequest detectInfoRequest){
        log.info("获取识别结果,请求参数:{},{}",
                detectInfoRequest.getBizToken(),
                detectInfoRequest.getOpenId());
        DetectInfoResponse detectInfoResponse = new DetectInfoResponse();
        try{
            // 实例化一个认证对象，入参需要传入腾讯云账户 SecretId 和 SecretKey，此处还需注意密钥对的保密
            // 代码泄露可能会导致 SecretId 和 SecretKey 泄露，并威胁账号下所有资源的安全性。以下代码示例仅供参考，建议采用更安全的方式来使用密钥，请参见：https://cloud.tencent.com/document/product/1278/85305
            // 密钥可前往官网控制台 https://console.cloud.tencent.com/cam/capi 进行获取
            Credential cred = new Credential(secretId, secretKey);
            // 实例化一个http选项，可选的，没有特殊需求可以跳过
            HttpProfile httpProfile = new HttpProfile();
            httpProfile.setEndpoint("faceid.tencentcloudapi.com");
            // 实例化一个client选项，可选的，没有特殊需求可以跳过
            ClientProfile clientProfile = new ClientProfile();
            clientProfile.setHttpProfile(httpProfile);
            // 实例化要请求产品的client对象,clientProfile是可选的
            FaceidClient client = new FaceidClient(cred, "", clientProfile);
            // 实例化一个请求对象,每个接口都会对应一个request对象
            GetDetectInfoEnhancedRequest req = new GetDetectInfoEnhancedRequest();
            req.setBizToken(detectInfoRequest.getBizToken());
            req.setRuleId(ruleId);
            req.setInfoType("1");
            // 返回的resp是一个GetDetectInfoEnhancedResponse的实例，与请求对象对应
            GetDetectInfoEnhancedResponse resp = client.GetDetectInfoEnhanced(req);
            // 输出json格式的字符串回包
            log.info("获取识别结果,结果:{}",GetDetectInfoEnhancedResponse.toJsonString(resp));
            detectInfoResponse.setErrCode(resp.getText().getErrCode());
            detectInfoResponse.setErrMsg(resp.getText().getErrMsg());
        } catch (TencentCloudSDKException e) {
            log.error("getDetectInfoEnhanced出现异常",e);
        }

        if(detectInfoResponse.getErrCode()==0){
            redisUtil.delete("face:result:openId");
            redisUtil.setEx("face:result:openId","1",1,TimeUnit.HOURS);
        }
        //挡板
//        detectInfoResponse.setErrCode(0L);
//        detectInfoResponse.setErrMsg("成功");

        return detectInfoResponse;
    }

    public void getWxaCodeUnlimited(HttpServletResponse response){
        String url = "https://api.weixin.qq.com/wxa/getwxacodeunlimit?" +
                "access_token="+getAccessToken();
        Map<String, Object> paramMap = new HashMap<>();
        paramMap.put("page", "pages/index/index");
        paramMap.put("scene", "a=1");
        paramMap.put("check_path", false);
        paramMap.put("env_version", "release");
        ResponseEntity<Resource> entity = null;
        try {
            //通过Resource类来接收content type [image/jpeg]的响应数据
            entity  = new RestTemplate().postForEntity(url, paramMap, Resource.class);
            Resource resource = entity.getBody();
            if(resource!=null){
                InputStream inputStream = resource.getInputStream();
                byte[] bytes = IOUtil.read(inputStream);
                response.getOutputStream().write(bytes);
                response.getOutputStream().flush();
                response.getOutputStream().close();
            }

        } catch (Exception e) {
            log.error("[更新access_token失败]",e);
        }
    }

//    /**
//     * 获取小程序的AccessToken
//     * @author LiuYuCheng
//     * @return 结果
//     */
//    public synchronized String getAccessToken(){
//        Object accessToken = redisUtil.get("wechat_access_token");
//        if(accessToken==null || "".equals(accessToken)){
//            return updateAccessToken();
//        }
//        return accessToken.toString();
//    }

//    private String updateAccessToken() {
//        String url = "https://api.weixin.qq.com/cgi-bin/token?" +
//                "grant_type=client_credential&appid={appId}&secret={appSecret}";
//        Map<String, Object> paramMap = new HashMap<>();
//        paramMap.put("appId", appId);
//        paramMap.put("appSecret", secret);
//        ResponseEntity<AccessTokenEntity> entity = null;
//        try {
//            entity = new RestTemplate().getForEntity(url, AccessTokenEntity.class, paramMap);
//        } catch (RestClientException e) {
//            log.info("[更新access_token失败]");
//            e.printStackTrace();
//            return "";
//        }
//        AccessTokenEntity tokenEntity = entity.getBody();
//        String accessToken = tokenEntity.getAccess_token();
//        redisUtil.setEx("wechat_access_token", accessToken, 7100, TimeUnit.SECONDS);
//        return accessToken;
//    }
























    /**
     * 签名算法
     * @param values 参数
     * @param ticket ticket
     * @return 签名
     */
    public static String sign(List<String> values, String ticket) { //values传ticket外的其他参数
        if (values == null) {
            throw new NullPointerException("values is null");
        }
        values.removeAll(Collections.singleton(null));// remove null
        values.add(ticket);
        Collections.sort(values);

        StringBuilder sb = new StringBuilder();
        for (String s : values) {
            sb.append(s);
        }
        return Hashing.sha1().hashString(sb, Charsets.UTF_8).toString().toUpperCase();
    }

    public static String getUIStr(String tmp, String pre) {
        String t = "";
        if (tmp.length() <= (32 - pre.length())) {
            t = pre + tmp;
        }
        for (int i = t.length(); i < 32; i++) {
            t = t + "0";
        }
        return t;
    }

    /**
     * 1.生成签名
     * @return 签名
     */
    public String getSign(){
        List<String> values = new ArrayList<>();
        String tmp = SnowFlake.getID();
        String userId = getUIStr(tmp, "userId");
        String orderNo = getUIStr(tmp, "orderNo");
        String nonce = getUIStr(tmp, "nonce");

        values.add(appId);
        values.add("1.0.0");
        values.add(nonce);
        values.add(orderNo);
        values.add(userId);
        return sign(values, getNONCETicket(userId));
    }

    /**
     * 2.获取结果
     * @return 结果
     */
    public LiveResultEntity getResult(){
        //生成签名
        List<String> values = new ArrayList<>();
        String tmp = SnowFlake.getID();
        String userId = getUIStr(tmp, "userId");
        String orderNo = getUIStr(tmp, "orderNo");
        String nonce = getUIStr(tmp, "nonce");
        values.add(appId);
        values.add("1.0.0");
        values.add(nonce);
        values.add(orderNo);
        values.add(userId);
        String sign = sign(values, getSignTicket());
        String url = "https://idasc.webank.com/api/server/getLiveResult" +
                "?app_id={appId}&version=1.0.0&nonce={nonce}&order_no={order_no}&sign={sign}&get_file=2";
        Map<String, Object> paramMap = new HashMap<>();
        paramMap.put("appId", appId);
        paramMap.put("nonce", nonce);
        paramMap.put("order_no", orderNo);
        paramMap.put("sign", sign);
        ResponseEntity<LiveResultEntity> entity = null;
        try {
            entity = new RestTemplate().getForEntity(url, LiveResultEntity.class, paramMap);
        } catch (RestClientException e) {
            log.info("[getResult失败]");
            e.printStackTrace();
            return null;
        }
        //
        LiveResultEntity entityBody = entity.getBody();
        return entityBody;
    }

    public String getNONCETicket(String userId){
        String url = "https://miniprogram-kyc.tencentcloudapi.com/api/oauth2/api_ticket" +
                "?app_id={appId}&access_token={access_token}&type=NONCE&version=1.0.0&user_id={user_id}";
        Map<String, Object> paramMap = new HashMap<>();
        paramMap.put("appId", appId);
        paramMap.put("access_token", getAccessToken());
        paramMap.put("user_id", userId);
        ResponseEntity<TicketEntity> entity = null;
        try {
            entity = new RestTemplate().getForEntity(url, TicketEntity.class, paramMap);
        } catch (RestClientException e) {
            log.info("[getNONCETicket失败]");
            e.printStackTrace();
            return "";
        }
        TicketEntity entityBody = entity.getBody();
        return entityBody.getValue();
    }

    public String getSignTicket(){
        Object signTicket = redisUtil.get("wechat_sign_ticket");
        if (signTicket == null || "".equals(signTicket)) {
            String url = "https://miniprogram-kyc.tencentcloudapi.com/api/oauth2/api_ticket" +
                    "?app_id={appId}&access_token={access_token}&type=SIGN&version=1.0.0";
            Map<String, Object> paramMap = new HashMap<>();
            paramMap.put("appId", appId);
            paramMap.put("access_token", getAccessToken());
            ResponseEntity<TicketEntity> entity = null;
            try {
                entity = new RestTemplate().getForEntity(url, TicketEntity.class, paramMap);
            } catch (RestClientException e) {
                log.info("[getSignTicket失败]");
                e.printStackTrace();
                return "";
            }
            TicketEntity entityBody = entity.getBody();
            String value = entityBody.getValue();
            redisUtil.setEx("wechat_sign_ticket", value, 19, TimeUnit.MINUTES);
            return value;
        }
        return signTicket.toString();
    }

    public static void main(String[] args) {
        for (int i = 0; i < 10; i++) {
            String timeId = IdWorker.getTimeId();
            System.out.println("timeId = " + timeId);
        }
    }
}
